package com.example.user.service.user;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.framework.common.constant.BaseHeaders;
import com.example.framework.common.exception.ServiceException;
import com.example.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.example.framework.common.util.EncryptUtils;
import com.example.framework.mq.kafka.core.util.KafkaProducerUtils;
import com.example.framework.security.core.LoginUser;
import com.example.framework.security.core.util.JwtTokenUtils;
import com.example.framework.security.core.util.SecurityFrameworkUtils;
import com.example.framework.web.core.util.WebFrameworkUtils;
import com.example.user.constants.ErrorCodeConstants;
import com.example.user.constants.UserConstants;
import com.example.user.controller.auth.vo.UserSourceType;
import com.example.user.controller.basic.vo.*;
import com.example.user.convert.UserConvert;
import com.example.user.dal.dataobject.user.UserDO;
import com.example.user.dal.mysql.permission.MenuMapper;
import com.example.user.dal.mysql.user.UserMapper;
import com.example.user.event.UpdateUserAvatarEvent;
import com.example.user.event.UpdateUsernameEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author yuzhenxin
 * @date 2023/3/27
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    @Value("${encrypt.aes.secret}")
    private String aesSecret;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private JwtTokenUtils jwtTokenUtils;

    @Resource
    private UserMapper userMapper;

    @Resource
    private MenuMapper menuMapper;

    @Override
    public boolean checkUserExists(UserSourceType reqVo) {
        LambdaQueryWrapper<UserDO> queryWrapper = UserService.buildUserQueryWrapper(reqVo, aesSecret);
        return this.count(queryWrapper) > 0;
    }

    @Override
    public UserInfoRespVO getUserInfo(String userId) {
        UserDO userDO = userMapper.selectOne(Wrappers.<UserDO>lambdaQuery()
                .select(UserDO::getUserId, UserDO::getName, UserDO::getMobile, UserDO::getEmail,
                        UserDO::getAvatar, UserDO::getAvatarTimestamp, UserDO::getCountryId)
                .eq(UserDO::getUserId, userId));
        if (Objects.isNull(userDO)) {
            throw ServiceException.of(ErrorCodeConstants.USER_NOT_FOUND);
        }

        return UserConvert.INSTANCE.covert(userDO)
                .setUserTel(EncryptUtils.AesDecrypt(userDO.getMobile(), aesSecret))
                .setEmail(EncryptUtils.AesDecrypt(userDO.getEmail(), aesSecret));
    }

    @Override
    public LoginUser loadLoginUserByUserSourceType(UserSourceType userSourceType) {
        UserDO userDO = this.getOne(UserService.buildUserQueryWrapper(userSourceType, aesSecret));
        if (Objects.isNull(userDO)) {
            throw ServiceException.of(GlobalErrorCodeConstants.USER_NOT_FOUND);
        }
        return new LoginUser()
                .setUsername(userDO.getUserId())
                .setPassword(userDO.getPassword())
                .setPermissions(menuMapper.selectPermissionsByUserId(userDO.getUserId()))
                .setNickname(userDO.getName())
                .setCountryId(userDO.getRegisterCountryId())
                .setDeviceId(Optional.ofNullable(WebFrameworkUtils.getRequest())
                        .map(request -> request.getHeader(BaseHeaders.X_DEVICE_ID))
                        .orElse("unknown"));
    }

    @Override
    public Set<String> getUserIds() {
        return userMapper.selectList(Wrappers.<UserDO>lambdaQuery().select(UserDO::getUserId))
                .stream()
                .map(UserDO::getUserId)
                .collect(Collectors.toSet());
    }

    @Override
    public boolean updateUserInfo(UserInfoUpdateVO userInfoUpdateVO) {
        if (Objects.isNull(userInfoUpdateVO.getArea())) {
            return false;
        }
        UserDO userDO = UserDO.builder().countryId(userInfoUpdateVO.getArea()).build();
        return this.update(userDO, Wrappers.<UserDO>lambdaUpdate()
                .eq(UserDO::getUserId, SecurityFrameworkUtils.getLoginUserId()));
    }

    @Override
    public boolean updateUserName(UserNameUpdateVO reqVO) {
        boolean updated = this.update(userDO -> userDO.setName(reqVO.getName()),
                true, loginUser -> loginUser.setNickname(reqVO.getName()));
        if (updated) {
            UpdateUsernameEvent event = new UpdateUsernameEvent(SecurityFrameworkUtils.getLoginUserId());
            KafkaProducerUtils.sendMessageAsync(UserConstants.UPDATE_USERNAME_EVENT, event);
        }
        return updated;
    }

    @Override
    public boolean updateUserAvatar(UserAvatarUpdateVO reqVO) {
        boolean updated = this.update(userDO -> userDO.setAvatar(reqVO.getAvatar()).setAvatarTimestamp(reqVO.getTimestamp()),
                false, null);
        if (updated) {
            UpdateUserAvatarEvent event = new UpdateUserAvatarEvent(SecurityFrameworkUtils.getLoginUserId());
            KafkaProducerUtils.sendMessageAsync(UserConstants.UPDATE_AVATAR_EVENT, event);
        }
        return updated;
    }

    private boolean update(Consumer<UserDO> userDOConsumer, boolean needUpdateRedis, Consumer<LoginUser> loginUserConsumer) {
        UserDO userDO = this.getOne(Wrappers.<UserDO>lambdaQuery().eq(UserDO::getUserId, SecurityFrameworkUtils.getLoginUserId()));
        if (Objects.isNull(userDO)) {
            throw ServiceException.of(ErrorCodeConstants.USER_NOT_FOUND);
        }

        // 因为其他字段不更新，无需传入整个实体类
        UserDO toUpdateUserDO = new UserDO().setId(userDO.getId());
        userDOConsumer.accept(toUpdateUserDO);
        boolean updated = this.updateById(toUpdateUserDO);

        if (updated && needUpdateRedis) {
            // 处理更新
            LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
            if (Objects.nonNull(loginUser)) {
                loginUserConsumer.accept(loginUser);
                // 更新缓存
                jwtTokenUtils.updateUserInfo(loginUser);
            }
        }

        return updated;
    }

    @Override
    public boolean updateUserPassword(UserPasswordUpdateVO reqVO) {
        String userId = SecurityFrameworkUtils.getLoginUserId();

        UserDO userDO = this.getOne(Wrappers.<UserDO>lambdaQuery()
                .select(UserDO::getId, UserDO::getPassword)
                .eq(UserDO::getUserId, userId));
        if (Objects.isNull(userDO)) {
            throw ServiceException.of(ErrorCodeConstants.USER_NOT_FOUND);
        }

        if (!passwordEncoder.matches(reqVO.getOldPassword(), userDO.getPassword())) {
            throw ServiceException.of(ErrorCodeConstants.USER_PASSWORD_ERROR);
        }
        // 密码相同就不修改了
        if (passwordEncoder.matches(reqVO.getPassword(), userDO.getPassword())) {
            return false;
        }
        userDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));

        boolean updated = this.updateById(userDO);
        // 清除缓存登录态，让其他端登录的用户强制退出
        jwtTokenUtils.removeToken(userId);

        return updated;
    }

}
